/** @file
PiSmmCommunication PEI Driver.

Copyright (c) 2010 - 2015, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include <PiPei.h>
#include <PiDxe.h>
#include <PiSmm.h>
#include <Library/PeiServicesTablePointerLib.h>
#include <Library/PeiServicesLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/HobLib.h>
#include <Library/DebugLib.h>
#include <Protocol/SmmCommunication.h>
#include <Ppi/SmmCommunication.h>
#include <Ppi/SmmAccess.h>
#include <Ppi/SmmControl.h>
#include <Guid/AcpiS3Context.h>

#include "PiSmmCommunicationPrivate.h"

/**
  the whole picture is below:

  +----------------------------------+
  | ACPI_VARIABLE_HOB                |
  |   SmramDescriptor                | <- DRAM
  |     CpuStart                     |---
  +----------------------------------+   |
                                         |
  +----------------------------------+<--
  | SMM_S3_RESUME_STATE              |
  |   Signature                      | <- SMRAM
  |   Smst                           |---
  +----------------------------------+   |
                                         |
  +----------------------------------+<--
  | EFI_SMM_SYSTEM_TABLE2            |
  |   NumberOfTableEntries           | <- SMRAM
  |   SmmConfigurationTable          |---
  +----------------------------------+   |
                                         |
  +----------------------------------+<--
  | EFI_SMM_COMMUNICATION_CONTEXT    |
  |   SwSmiNumber                    | <- SMRAM
  |   BufferPtrAddress               |----------------
  +----------------------------------+                |
                                                      |
  +----------------------------------+                |
  | EFI_SMM_COMMUNICATION_ACPI_TABLE |                |
  |   SwSmiNumber                    | <- AcpiTable   |
  |   BufferPtrAddress               |---             |
  +----------------------------------+   |            |
                                         |            |
  +----------------------------------+<---------------
  | Communication Buffer Pointer     | <- AcpiNvs
  +----------------------------------+---
                                         |
  +----------------------------------+<--
  | EFI_SMM_COMMUNICATE_HEADER       |
  |   HeaderGuid                     | <- DRAM
  |   MessageLength                  |
  +----------------------------------+

**/

#if defined (MDE_CPU_IA32)
typedef struct {
  EFI_TABLE_HEADER    Hdr;
  UINT64              SmmFirmwareVendor;
  UINT64              SmmFirmwareRevision;
  UINT64              SmmInstallConfigurationTable;
  UINT64              SmmIoMemRead;
  UINT64              SmmIoMemWrite;
  UINT64              SmmIoIoRead;
  UINT64              SmmIoIoWrite;
  UINT64              SmmAllocatePool;
  UINT64              SmmFreePool;
  UINT64              SmmAllocatePages;
  UINT64              SmmFreePages;
  UINT64              SmmStartupThisAp;
  UINT64              CurrentlyExecutingCpu;
  UINT64              NumberOfCpus;
  UINT64              CpuSaveStateSize;
  UINT64              CpuSaveState;
  UINT64              NumberOfTableEntries;
  UINT64              SmmConfigurationTable;
} EFI_SMM_SYSTEM_TABLE2_64;

typedef struct {
  EFI_GUID                          VendorGuid;
  UINT64                            VendorTable;
} EFI_CONFIGURATION_TABLE64;
#endif

#if defined (MDE_CPU_X64)
typedef EFI_SMM_SYSTEM_TABLE2 EFI_SMM_SYSTEM_TABLE2_64;
typedef EFI_CONFIGURATION_TABLE EFI_CONFIGURATION_TABLE64;
#endif

/**
  Communicates with a registered handler.

  This function provides a service to send and receive messages from a registered UEFI service.

  @param[in] This                The EFI_PEI_SMM_COMMUNICATION_PPI instance.
  @param[in, out] CommBuffer     A pointer to the buffer to convey into SMRAM.
  @param[in, out] CommSize       The size of the data buffer being passed in.On exit, the size of data
                                 being returned. Zero if the handler does not wish to reply with any data.

  @retval EFI_SUCCESS            The message was successfully posted.
  @retval EFI_INVALID_PARAMETER  The CommBuffer was NULL.
  @retval EFI_NOT_STARTED        The service is NOT started.
**/
EFI_STATUS
EFIAPI
Communicate (
  IN CONST EFI_PEI_SMM_COMMUNICATION_PPI   *This,
  IN OUT VOID                              *CommBuffer,
  IN OUT UINTN                             *CommSize
  );

EFI_PEI_SMM_COMMUNICATION_PPI      mSmmCommunicationPpi = { Communicate };

EFI_PEI_PPI_DESCRIPTOR mPpiList = {
  (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEfiPeiSmmCommunicationPpiGuid,
  &mSmmCommunicationPpi
};

/**
  Get SMM communication context.

  @return SMM communication context.
**/
EFI_SMM_COMMUNICATION_CONTEXT *
GetCommunicationContext (
  VOID
  )
{
  EFI_HOB_GUID_TYPE                *GuidHob;
  EFI_SMM_COMMUNICATION_CONTEXT    *SmmCommunicationContext;

  GuidHob = GetFirstGuidHob (&gEfiPeiSmmCommunicationPpiGuid);
  ASSERT (GuidHob != NULL);

  SmmCommunicationContext = (EFI_SMM_COMMUNICATION_CONTEXT *)GET_GUID_HOB_DATA (GuidHob);

  return SmmCommunicationContext;
}

/**
  Set SMM communication context.

  @param SmmCommunicationContext SMM communication context.
**/
VOID
SetCommunicationContext (
  IN EFI_SMM_COMMUNICATION_CONTEXT    *SmmCommunicationContext
  )
{
  EFI_PEI_HOB_POINTERS             Hob;
  UINTN                            BufferSize;

  BufferSize = sizeof (*SmmCommunicationContext);
  Hob.Raw = BuildGuidHob (
              &gEfiPeiSmmCommunicationPpiGuid,
              BufferSize
              );
  ASSERT (Hob.Raw);

  CopyMem ((VOID *)Hob.Raw, SmmCommunicationContext, sizeof(*SmmCommunicationContext));
}

/**
  Get VendorTable by VendorGuid in Smst.

  @param Signature  Signature of SMM_S3_RESUME_STATE
  @param Smst       SMM system table
  @param VendorGuid vendor guid

  @return vendor table.
**/
VOID *
InternalSmstGetVendorTableByGuid (
  IN UINT64                                        Signature,
  IN EFI_SMM_SYSTEM_TABLE2                         *Smst,
  IN EFI_GUID                                      *VendorGuid
  )
{
  EFI_CONFIGURATION_TABLE                       *SmmConfigurationTable;
  UINTN                                         NumberOfTableEntries;
  UINTN                                         Index;
  EFI_SMM_SYSTEM_TABLE2_64                      *Smst64;
  EFI_CONFIGURATION_TABLE64                     *SmmConfigurationTable64;

  if ((sizeof(UINTN) == sizeof(UINT32)) && (Signature == SMM_S3_RESUME_SMM_64)) {
    //
    // 32 PEI + 64 DXE
    //
    Smst64 = (EFI_SMM_SYSTEM_TABLE2_64 *)Smst;
    DEBUG ((EFI_D_INFO, "InitCommunicationContext - SmmConfigurationTable: %x\n", Smst64->SmmConfigurationTable));
    DEBUG ((EFI_D_INFO, "InitCommunicationContext - NumberOfTableEntries: %x\n", Smst64->NumberOfTableEntries));
    SmmConfigurationTable64 = (EFI_CONFIGURATION_TABLE64 *)(UINTN)Smst64->SmmConfigurationTable;
    NumberOfTableEntries = (UINTN)Smst64->NumberOfTableEntries;
    for (Index = 0; Index < NumberOfTableEntries; Index++) {
      if (CompareGuid (&SmmConfigurationTable64[Index].VendorGuid, VendorGuid)) {
        return (VOID *)(UINTN)SmmConfigurationTable64[Index].VendorTable;
      }
    }
    return NULL;
  } else {
    DEBUG ((EFI_D_INFO, "InitCommunicationContext - SmmConfigurationTable: %x\n", Smst->SmmConfigurationTable));
    DEBUG ((EFI_D_INFO, "InitCommunicationContext - NumberOfTableEntries: %x\n", Smst->NumberOfTableEntries));
    SmmConfigurationTable = Smst->SmmConfigurationTable;
    NumberOfTableEntries = Smst->NumberOfTableEntries;
    for (Index = 0; Index < NumberOfTableEntries; Index++) {
      if (CompareGuid (&SmmConfigurationTable[Index].VendorGuid, VendorGuid)) {
        return (VOID *)SmmConfigurationTable[Index].VendorTable;
      }
    }
    return NULL;
  }
}

/**
  Init SMM communication context.
**/
VOID
InitCommunicationContext (
  VOID
  )
{
  EFI_SMRAM_DESCRIPTOR                          *SmramDescriptor;
  SMM_S3_RESUME_STATE                           *SmmS3ResumeState;
  VOID                                          *GuidHob;
  EFI_SMM_COMMUNICATION_CONTEXT                 *SmmCommunicationContext;

  GuidHob = GetFirstGuidHob (&gEfiAcpiVariableGuid);
  ASSERT (GuidHob != NULL);
  SmramDescriptor = (EFI_SMRAM_DESCRIPTOR *) GET_GUID_HOB_DATA (GuidHob);
  SmmS3ResumeState = (SMM_S3_RESUME_STATE *)(UINTN)SmramDescriptor->CpuStart;

  DEBUG ((EFI_D_INFO, "InitCommunicationContext - SmmS3ResumeState: %x\n", SmmS3ResumeState));
  DEBUG ((EFI_D_INFO, "InitCommunicationContext - Smst: %x\n", SmmS3ResumeState->Smst));

  SmmCommunicationContext = (EFI_SMM_COMMUNICATION_CONTEXT *)InternalSmstGetVendorTableByGuid (
                                                               SmmS3ResumeState->Signature,
                                                               (EFI_SMM_SYSTEM_TABLE2 *)(UINTN)SmmS3ResumeState->Smst,
                                                               &gEfiPeiSmmCommunicationPpiGuid
                                                               );
  ASSERT (SmmCommunicationContext != NULL);

  SetCommunicationContext (SmmCommunicationContext);

  return ;
}

/**
  Communicates with a registered handler.

  This function provides a service to send and receive messages from a registered UEFI service.

  @param[in] This                The EFI_PEI_SMM_COMMUNICATION_PPI instance.
  @param[in, out] CommBuffer     A pointer to the buffer to convey into SMRAM.
  @param[in, out] CommSize       The size of the data buffer being passed in.On exit, the size of data
                                 being returned. Zero if the handler does not wish to reply with any data.

  @retval EFI_SUCCESS            The message was successfully posted.
  @retval EFI_INVALID_PARAMETER  The CommBuffer was NULL.
  @retval EFI_NOT_STARTED        The service is NOT started.
**/
EFI_STATUS
EFIAPI
Communicate (
  IN CONST EFI_PEI_SMM_COMMUNICATION_PPI   *This,
  IN OUT VOID                              *CommBuffer,
  IN OUT UINTN                             *CommSize
  )
{
  EFI_STATUS                       Status;
  PEI_SMM_CONTROL_PPI              *SmmControl;
  PEI_SMM_ACCESS_PPI               *SmmAccess;
  UINT8                            SmiCommand;
  UINTN                            Size;
  EFI_SMM_COMMUNICATION_CONTEXT    *SmmCommunicationContext;

  DEBUG ((EFI_D_INFO, "PiSmmCommunicationPei Communicate Enter\n"));

  if (CommBuffer == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Get needed resource
  //
  Status = PeiServicesLocatePpi (
             &gPeiSmmControlPpiGuid,
             0,
             NULL,
             (VOID **)&SmmControl
             );
  if (EFI_ERROR (Status)) {
    return EFI_NOT_STARTED;
  }

  Status = PeiServicesLocatePpi (
             &gPeiSmmAccessPpiGuid,
             0,
             NULL,
             (VOID **)&SmmAccess
             );
  if (EFI_ERROR (Status)) {
    return EFI_NOT_STARTED;
  }

  //
  // Check SMRAM locked, it should be done after SMRAM lock.
  //
  if (!SmmAccess->LockState) {
    DEBUG ((EFI_D_INFO, "PiSmmCommunicationPei LockState - %x\n", (UINTN)SmmAccess->LockState));
    return EFI_NOT_STARTED;
  }

  SmmCommunicationContext = GetCommunicationContext ();
  DEBUG ((EFI_D_INFO, "PiSmmCommunicationPei BufferPtrAddress - 0x%016lx, BufferPtr: 0x%016lx\n", SmmCommunicationContext->BufferPtrAddress, *(EFI_PHYSICAL_ADDRESS *)(UINTN)SmmCommunicationContext->BufferPtrAddress));

  //
  // No need to check if BufferPtr is 0, because it is in PEI phase.
  //
  *(EFI_PHYSICAL_ADDRESS *)(UINTN)SmmCommunicationContext->BufferPtrAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)CommBuffer;
  DEBUG ((EFI_D_INFO, "PiSmmCommunicationPei CommBuffer - %x\n", (UINTN)CommBuffer));

  //
  // Send command
  //
  SmiCommand = (UINT8)SmmCommunicationContext->SwSmiNumber;
  Size = sizeof(SmiCommand);
  Status = SmmControl->Trigger (
                         (EFI_PEI_SERVICES **)GetPeiServicesTablePointer (),
                         SmmControl,
                         (INT8 *)&SmiCommand,
                         &Size,
                         FALSE,
                         0
                         );
  ASSERT_EFI_ERROR (Status);

  //
  // Setting BufferPtr to 0 means this transaction is done.
  //
  *(EFI_PHYSICAL_ADDRESS *)(UINTN)SmmCommunicationContext->BufferPtrAddress = 0;

  DEBUG ((EFI_D_INFO, "PiSmmCommunicationPei Communicate Exit\n"));

  return EFI_SUCCESS;
}

/**
  Entry Point for PI SMM communication PEIM.

  @param  FileHandle              Handle of the file being invoked.
  @param  PeiServices             Pointer to PEI Services table.

  @retval EFI_SUCEESS
  @return Others          Some error occurs.
**/
EFI_STATUS
EFIAPI
PiSmmCommunicationPeiEntryPoint (
  IN EFI_PEI_FILE_HANDLE       FileHandle,
  IN CONST EFI_PEI_SERVICES    **PeiServices
  )
{
  EFI_STATUS                      Status;
  PEI_SMM_ACCESS_PPI              *SmmAccess;
  EFI_BOOT_MODE                   BootMode;
  UINTN                           Index;

  BootMode = GetBootModeHob ();
  if (BootMode != BOOT_ON_S3_RESUME) {
    return EFI_UNSUPPORTED;
  }

  Status = PeiServicesLocatePpi (
             &gPeiSmmAccessPpiGuid,
             0,
             NULL,
             (VOID **)&SmmAccess
             );
  if (EFI_ERROR (Status)) {
    return EFI_NOT_STARTED;
  }

  //
  // Check SMRAM locked, it should be done before SMRAM lock.
  //
  if (SmmAccess->LockState) {
    DEBUG ((EFI_D_INFO, "PiSmmCommunicationPei LockState - %x\n", (UINTN)SmmAccess->LockState));
    return EFI_ACCESS_DENIED;
  }

  //
  // Open all SMRAM
  //
  for (Index = 0; !EFI_ERROR (Status); Index++) {
    Status = SmmAccess->Open ((EFI_PEI_SERVICES **)GetPeiServicesTablePointer (), SmmAccess, Index);
  }

  InitCommunicationContext ();

  PeiServicesInstallPpi (&mPpiList);

  return RETURN_SUCCESS;
}
